Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / core / network / src / commonTest / kotlin / org / meshtastic / core / network / radio / BleReconnectPolicyTest.kt

Displaying Raw • Download

core/network/src/commonTest/kotlin/org/meshtastic/core/network/radio/BleReconnectPolicyTest.kt bf929c20f274232e0eb8ddc19b2dc82403e99e5b (bf929c20) Text, 14.12 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.network.radio

Tff7b72import T7ee787kotlinx.coroutines.ExperimentalCoroutinesApi
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.test.advanceTimeBy
Tff7b72import T7ee787kotlinx.coroutines.test.advanceUntilIdle
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72import T7ee787kotlin.time.Duration.Companion.milliseconds
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds

Tff7b72class T56d364BleReconnectPolicyTest Tb4b4b4{

Tf0883e@Test
Tff7b72fun Td2a8ff`stable disconnect resets failures and returns Continue`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Tb4b4b4)
T8b949e// Simulate one prior failure
Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)

T8b949e// Now a stable disconnect should reset
Tff7b72val Te6edf3action Tff7b72=
Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72trueTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72falseTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3ContinueTb4b4b4, Te6edf3actionTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`intentional disconnect resets failures and returns Continue`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Tb4b4b4)
Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3action Tff7b72=
Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72falseTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3ContinueTb4b4b4, Te6edf3actionTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`unstable disconnect increments failures`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3action Tff7b72=
Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72falseTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72falseTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3action Tff7b72is Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3RetryTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`failure at threshold signals transient disconnect`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Te6edf3failureThreshold Tff7b72= T79c0ff3Tb4b4b4)
T8b949e// Accumulate failures up to threshold
Te6edf3repeatTb4b4b4(T79c0ff2Tb4b4b4) Tb4b4b4{ Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3action Tff7b72= Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3action Tff7b72is Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3SignalTransientTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`failure at max gives up permanently`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Te6edf3maxFailures Tff7b72= T79c0ff3Tb4b4b4)
Te6edf3repeatTb4b4b4(T79c0ff2Tb4b4b4) Tb4b4b4{ Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3action Tff7b72= Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3GiveUpTb4b4b4, Te6edf3actionTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`backoff increases with consecutive failures`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3backoffs Tff7b72=
Tb4b4b4(T79c0ff1.Tb4b4b4.T79c0ff5Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72>
Tff7b72val Te6edf3action Tff7b72= Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72when Tb4b4b4(Te6edf3actionTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3Retry Tff7b72-Tff7b72> Te6edf3actionTb4b4b4.Te6edf3backoff
Tff7b72is Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3SignalTransient Tff7b72-Tff7b72> Te6edf3actionTb4b4b4.Te6edf3backoff
Tff7b72else Tff7b72-Tff7b72> Te6edf3errorTb4b4b4(Ta5d6ff"Ta5d6ffUnexpected action: Tffd700$Te6edf3actionTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// Verify backoffs are non-decreasing
Tff7b72for Tb4b4b4(Te6edf3i Tff7b72in T79c0ff0 Te6edf3until Te6edf3backoffsTb4b4b4.Te6edf3size Tff7b72- T79c0ff1Tb4b4b4) Tb4b4b4{
Te6edf3assertTrueTb4b4b4(Te6edf3backoffsTff7b72[Te6edf3iTff7b72] Tff7b72<Tff7b72= Te6edf3backoffsTff7b72[Te6edf3i Tff7b72+ T79c0ff1Tff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffExpected Tffd700${Te6edf3backoffsTff7b72[Te6edf3iTff7b72]Tffd700}Ta5d6ff <= Tffd700${Te6edf3backoffsTff7b72[Te6edf3i Tff7b72+ T79c0ff1Tff7b72]Tffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`custom backoff strategy is used`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3customBackoff Tff7b72= T79c0ff4T79c0ff2.Te6edf3seconds
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Te6edf3backoffStrategy Tff7b72= Tb4b4b4{ Te6edf3customBackoff Tb4b4b4}Tb4b4b4)
Tff7b72val Te6edf3action Tff7b72= Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3action Tff7b72is Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3RetryTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3customBackoffTb4b4b4, Te6edf3actionTb4b4b4.Te6edf3backoffTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`maxFailures equal to failureThreshold gives up without signalling transient`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Te6edf3maxFailures Tff7b72= T79c0ff3Tb4b4b4, Te6edf3failureThreshold Tff7b72= T79c0ff3Tb4b4b4)
Te6edf3repeatTb4b4b4(T79c0ff2Tb4b4b4) Tb4b4b4{ Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3action Tff7b72= Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
T8b949e// GiveUp takes priority over SignalTransient when both thresholds are the same
Te6edf3assertEqualsTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3ActionTb4b4b4.Te6edf3GiveUpTb4b4b4, Te6edf3actionTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`failure count resets after stable disconnect then re-increments`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72= Te6edf3BleReconnectPolicyTb4b4b4(Tb4b4b4)
T8b949e// Accumulate two failures
Te6edf3repeatTb4b4b4(T79c0ff2Tb4b4b4) Tb4b4b4{ Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)

T8b949e// Stable disconnect resets
Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72trueTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72falseTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)

T8b949e// New failure starts from 1
Te6edf3policyTb4b4b4.Te6edf3processOutcomeTb4b4b4(Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
Tb4b4b4}

T8b949e// region execute() loop tests

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tf0883e@Test
Tff7b72fun Td2a8ff`execute gives up after maxFailures and calls onPermanentDisconnect`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72=
Te6edf3BleReconnectPolicyTb4b4b4(Te6edf3maxFailures Tff7b72= T79c0ff3Tb4b4b4, Te6edf3settleDelay Tff7b72= T79c0ff1.Te6edf3millisecondsTb4b4b4, Te6edf3backoffStrategy Tff7b72= Tb4b4b4{ T79c0ff1.Te6edf3milliseconds Tb4b4b4}Tb4b4b4)
Tff7b72var Te6edf3permanentErrorTb4b4b4: Te6edf3Throwable? Tff7b72= Tff7b72null
Tff7b72var Te6edf3permanentCalled Tff7b72= Tff7b72false
Tff7b72var Te6edf3transientCalled Tff7b72= Tff7b72false

Te6edf3policyTb4b4b4.Te6edf3executeTb4b4b4(
Te6edf3attempt Tff7b72= Tb4b4b4{ Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6ffconnection failedTa5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onTransientDisconnect Tff7b72= Tb4b4b4{ Te6edf3transientCalled Tff7b72= Tff7b72true Tb4b4b4}Tb4b4b4,
Te6edf3onPermanentDisconnect Tff7b72= Tb4b4b4{ Te6edf3error Tff7b72-Tff7b72>
Te6edf3permanentCalled Tff7b72= Tff7b72true
Te6edf3permanentError Tff7b72= Te6edf3error
Tb4b4b4}Tb4b4b4,
Tb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3permanentCalledTb4b4b4, Ta5d6ff"Ta5d6ffonPermanentDisconnect should have been calledTa5d6ff"Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3permanentErrorTb4b4b4, Ta5d6ff"Ta5d6fferror should be passed to onPermanentDisconnectTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffconnection failedTa5d6ff"Tb4b4b4, Te6edf3permanentErrorTff7b72?.Te6edf3messageTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
T8b949e// failureThreshold defaults to 3, same as maxFailures here, so GiveUp takes priority
Te6edf3assertTrueTb4b4b4(Tff7b72!Te6edf3transientCalledTb4b4b4, Ta5d6ff"Ta5d6ffonTransientDisconnect should not be called when GiveUp fires firstTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tf0883e@Test
Tff7b72fun Td2a8ff`execute calls onTransientDisconnect at threshold then continues retrying`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attemptCount Tff7b72= T79c0ff0
Tff7b72val Te6edf3policy Tff7b72=
Te6edf3BleReconnectPolicyTb4b4b4(
Te6edf3maxFailures Tff7b72= T79c0ff5Tb4b4b4,
Te6edf3failureThreshold Tff7b72= T79c0ff2Tb4b4b4,
Te6edf3settleDelay Tff7b72= T79c0ff1.Te6edf3millisecondsTb4b4b4,
Te6edf3backoffStrategy Tff7b72= Tb4b4b4{ T79c0ff1.Te6edf3milliseconds Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72var Te6edf3transientCount Tff7b72= T79c0ff0

Te6edf3policyTb4b4b4.Te6edf3executeTb4b4b4(
Te6edf3attempt Tff7b72= Tb4b4b4{
Te6edf3attemptCountTff7b72+Tff7b72+
Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fffail #Tffd700$Te6edf3attemptCountTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}Tb4b4b4,
Te6edf3onTransientDisconnect Tff7b72= Tb4b4b4{ Te6edf3transientCountTff7b72+Tff7b72+ Tb4b4b4}Tb4b4b4,
Te6edf3onPermanentDisconnect Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff5Tb4b4b4, Te6edf3attemptCountTb4b4b4, Ta5d6ff"Ta5d6ffshould attempt exactly maxFailures timesTa5d6ff"Tb4b4b4)
T8b949e// Transient is signalled for failures 2, 3, 4 (at or above threshold, below maxFailures)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3transientCountTb4b4b4, Ta5d6ff"Ta5d6ffshould signal transient for each failure at or above thresholdTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tf0883e@Test
Tff7b72fun Td2a8ff`execute continues immediately after stable disconnect`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attemptCount Tff7b72= T79c0ff0
Tff7b72val Te6edf3policy Tff7b72=
Te6edf3BleReconnectPolicyTb4b4b4(Te6edf3maxFailures Tff7b72= T79c0ff5Tb4b4b4, Te6edf3settleDelay Tff7b72= T79c0ff1.Te6edf3millisecondsTb4b4b4, Te6edf3backoffStrategy Tff7b72= Tb4b4b4{ T79c0ff1.Te6edf3milliseconds Tb4b4b4}Tb4b4b4)

Te6edf3policyTb4b4b4.Te6edf3executeTb4b4b4(
Te6edf3attempt Tff7b72= Tb4b4b4{
Te6edf3attemptCountTff7b72+Tff7b72+
Tff7b72if Tb4b4b4(Te6edf3attemptCount Tff7b72<Tff7b72= T79c0ff2Tb4b4b4) Tb4b4b4{
T8b949e// First two attempts connect briefly and disconnect stably
Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72trueTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72falseTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Then fail until maxFailures
Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6fffailTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Te6edf3onTransientDisconnect Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Te6edf3onPermanentDisconnect Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Tb4b4b4)

T8b949e// 2 stable disconnects + 5 failures (counter resets after each stable, so needs 5 more to hit max)
Te6edf3assertEqualsTb4b4b4(T79c0ff7Tb4b4b4, Te6edf3attemptCountTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff5Tb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
Tb4b4b4}

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tf0883e@Test
Tff7b72fun Td2a8ff`execute passes null error for unstable disconnect at threshold`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3policy Tff7b72=
Te6edf3BleReconnectPolicyTb4b4b4(
Te6edf3maxFailures Tff7b72= T79c0ff5Tb4b4b4,
Te6edf3failureThreshold Tff7b72= T79c0ff2Tb4b4b4,
Te6edf3settleDelay Tff7b72= T79c0ff1.Te6edf3millisecondsTb4b4b4,
Te6edf3backoffStrategy Tff7b72= Tb4b4b4{ T79c0ff1.Te6edf3milliseconds Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3transientErrors Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3Throwable?Tff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72var Te6edf3attemptCount Tff7b72= T79c0ff0

Te6edf3policyTb4b4b4.Te6edf3executeTb4b4b4(
Te6edf3attempt Tff7b72= Tb4b4b4{
Te6edf3attemptCountTff7b72+Tff7b72+
T8b949e// Use unstable disconnects (not Failed) so lastError is null
Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72falseTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72falseTb4b4b4)
Tb4b4b4}Tb4b4b4,
Te6edf3onTransientDisconnect Tff7b72= Tb4b4b4{ Te6edf3error Tff7b72-Tff7b72> Te6edf3transientErrorsTb4b4b4.Te6edf3addTb4b4b4(Te6edf3errorTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onPermanentDisconnect Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Tb4b4b4)

T8b949e// Disconnected outcomes don't have errors, so all transient callbacks get null
Te6edf3assertTrueTb4b4b4(Te6edf3transientErrorsTb4b4b4.Te6edf3all Tb4b4b4{ Tffa657it Tff7b72=Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffDisconnected outcomes should pass null errorTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tf0883e@Test
Tff7b72fun Td2a8ff`execute reaches recovery after repeated inner connect failures`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3failedInnerCyclesBeforeRecovery Tff7b72= T79c0ff5
Tff7b72val Te6edf3maxFailuresAfterRecovery Tff7b72= T79c0ff7
Tff7b72val Te6edf3policy Tff7b72=
Te6edf3BleReconnectPolicyTb4b4b4(
Te6edf3maxFailures Tff7b72= Te6edf3maxFailuresAfterRecoveryTb4b4b4,
Te6edf3failureThreshold Tff7b72= T79c0ff3Tb4b4b4,
Te6edf3settleDelay Tff7b72= T79c0ff1.Te6edf3millisecondsTb4b4b4,
Te6edf3backoffStrategy Tff7b72= Tb4b4b4{ T79c0ff1.Te6edf3milliseconds Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72var Te6edf3attemptCount Tff7b72= T79c0ff0
Tff7b72var Te6edf3reachedRecoveryAttempt Tff7b72= Tff7b72false
Tff7b72var Te6edf3transientBeforeRecovery Tff7b72= T79c0ff0
Tff7b72var Te6edf3transientAfterRecovery Tff7b72= T79c0ff0
Tff7b72var Te6edf3permanentCalled Tff7b72= Tff7b72false

Te6edf3policyTb4b4b4.Te6edf3executeTb4b4b4(
Te6edf3attempt Tff7b72= Tb4b4b4{
Te6edf3attemptCountTff7b72+Tff7b72+
Tff7b72when Tb4b4b4{
Te6edf3attemptCount Tff7b72<Tff7b72= Te6edf3failedInnerCyclesBeforeRecovery Tff7b72-Tff7b72>
Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(
Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6ffbounded inner Kable cycle #Tffd700$Te6edf3attemptCountTa5d6ff failedTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72!Te6edf3reachedRecoveryAttempt Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3reachedRecoveryAttempt Tff7b72= Tff7b72true
Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72trueTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72falseTb4b4b4)
Tb4b4b4}

Tff7b72else Tff7b72-Tff7b72> Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6ffpost-recovery failureTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Te6edf3onTransientDisconnect Tff7b72= Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3reachedRecoveryAttemptTb4b4b4) Tb4b4b4{
Te6edf3transientAfterRecoveryTff7b72+Tff7b72+
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3transientBeforeRecoveryTff7b72+Tff7b72+
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Te6edf3onPermanentDisconnect Tff7b72= Tb4b4b4{ Te6edf3permanentCalled Tff7b72= Tff7b72true Tb4b4b4}Tb4b4b4,
Tb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3reachedRecoveryAttemptTb4b4b4, Ta5d6ff"Ta5d6ffouter reconnect must reach a later successful connection attemptTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(
Te6edf3failedInnerCyclesBeforeRecovery Tff7b72+ T79c0ff1 Tff7b72+ Te6edf3maxFailuresAfterRecoveryTb4b4b4,
Te6edf3attemptCountTb4b4b4,
Ta5d6ff"Ta5d6ffouter reconnect should continue through failed inner cycles before and after recoveryTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3transientBeforeRecoveryTb4b4b4, Ta5d6ff"Ta5d6fffailures at threshold and above should signal transient stateTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4Tb4b4b4, Te6edf3transientAfterRecoveryTb4b4b4, Ta5d6ff"Ta5d6ffstable recovery should reset the failure thresholdTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3permanentCalledTb4b4b4, Ta5d6ff"Ta5d6fffinite maxFailures is used only to terminate this testTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3maxFailuresAfterRecoveryTb4b4b4, Te6edf3policyTb4b4b4.Te6edf3consecutiveFailuresTb4b4b4)
Tb4b4b4}

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tf0883e@Test
Tff7b72fun Td2a8ff`execute stops when coroutine is cancelled`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attemptCount Tff7b72= T79c0ff0
Tff7b72val Te6edf3policy Tff7b72=
Te6edf3BleReconnectPolicyTb4b4b4(Te6edf3maxFailures Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4, Te6edf3settleDelay Tff7b72= T79c0ff1.Te6edf3millisecondsTb4b4b4, Te6edf3backoffStrategy Tff7b72= Tb4b4b4{ T79c0ff1.Te6edf3milliseconds Tb4b4b4}Tb4b4b4)

Tff7b72val Te6edf3job Tff7b72=
Te6edf3backgroundScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Te6edf3policyTb4b4b4.Te6edf3executeTb4b4b4(
Te6edf3attempt Tff7b72= Tb4b4b4{
Te6edf3attemptCountTff7b72+Tff7b72+
T8b949e// Always succeed stably — loop should run until cancelled
Te6edf3BleReconnectPolicyTb4b4b4.Te6edf3OutcomeTb4b4b4.Te6edf3DisconnectedTb4b4b4(Te6edf3wasStable Tff7b72= Tff7b72trueTb4b4b4, Te6edf3wasIntentional Tff7b72= Tff7b72falseTb4b4b4)
Tb4b4b4}Tb4b4b4,
Te6edf3onTransientDisconnect Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Te6edf3onPermanentDisconnect Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

T8b949e// Let a few iterations run, then cancel
Te6edf3advanceTimeByTb4b4b4(T79c0ff5T79c0ff0Tb4b4b4)
Te6edf3jobTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

T8b949e// Should have made some attempts but not reached maxFailures
Te6edf3assertTrueTb4b4b4(Te6edf3attemptCount Tff7b72> T79c0ff0Tb4b4b4, Ta5d6ff"Ta5d6ffshould have attempted at least onceTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3attemptCount Tff7b72< T79c0ff1T79c0ff0T79c0ff0Tb4b4b4, Ta5d6ff"Ta5d6ffshould not have exhausted all failures — was cancelledTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// endregion
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.44s